﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Multiprocessing.Core.Mathematics.Graphs
{
    public class Graph<TNodeKey, TNodeValue, TRibValue> 
        : IGraph<TNodeKey, TNodeValue, TRibValue>
    {
        #region Fields

        private readonly IDictionary<TNodeKey, GraphNode<TNodeKey, TNodeValue, TRibValue>> _nodes;

        #endregion

        #region Properties

        public GraphNode<TNodeKey, TNodeValue, TRibValue> this[TNodeKey key]
        {
            get { return _nodes[key]; }
            set { _nodes[key] = value; }
        }

        public int Count
        {
            get { return _nodes.Count; }
        }

        public IEnumerable<DirectionalRib<TNodeKey, TNodeValue, TRibValue>> Ribs
        {
            get { return _nodes.Values.SelectMany(node => node.Ribs); }
        }

        #endregion

        #region Constructors

        public Graph()
            : this(16)
        {
        }

        public Graph(int capacity)
        {
            _nodes = new Dictionary<TNodeKey, GraphNode<TNodeKey, TNodeValue, TRibValue>>(capacity);
        }

        public Graph(IEnumerable<GraphNode<TNodeKey, TNodeValue, TRibValue>> nodes)
        {
            if (ReferenceEquals(nodes, null))
            {
                throw new ArgumentNullException("nodes");
            }
            _nodes = nodes.ToDictionary(node => node.Key, node => node);
        }

        public Graph(IEnumerable<KeyValuePair<TNodeKey, TNodeValue>> nodes)
        {
            if (ReferenceEquals(nodes, null))
            {
                throw new ArgumentNullException("nodes");
            }
            _nodes = nodes.ToDictionary(pair => pair.Key,
                                        pair => new GraphNode<TNodeKey, TNodeValue, TRibValue>(pair.Key, pair.Value));
        }


        #endregion

        #region Methods

        public IEnumerator<GraphNode<TNodeKey, TNodeValue, TRibValue>> GetEnumerator()
        {
            return _nodes.Values.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public void Add(TNodeKey key, TNodeValue value)
        {
            this.Add(new GraphNode<TNodeKey, TNodeValue, TRibValue>(key, value));
        }

        public void Add(GraphNode<TNodeKey, TNodeValue, TRibValue> node)
        {
            if (ReferenceEquals(node, null))
            {
                throw new ArgumentNullException();
            }
            _nodes.Add(node.Key, node);
        }

        public bool TryGetValue(TNodeKey key, out GraphNode<TNodeKey, TNodeValue, TRibValue> node)
        {
            return _nodes.TryGetValue(key, out node);
        }

        public void AddRib(GraphNode<TNodeKey, TNodeValue, TRibValue> @from, GraphNode<TNodeKey, TNodeValue, TRibValue> to, TRibValue weight)
        {
            if (ReferenceEquals(@from, null))
            {
                throw new ArgumentNullException("from");
            }
            if (ReferenceEquals(@to, null))
            {
                throw new ArgumentNullException("to");
            }
            @from.AddRibTo(@to, weight);
        }

        public void AddRib(TNodeKey @from, TNodeKey to, TRibValue weight)
        {
            _nodes[@from].AddRibTo(_nodes[@to], weight);
        }

        public bool RemoveNode(TNodeKey key)
        {
            return _nodes.Remove(key);
        }

        public bool RemoveRib(TNodeKey first, TNodeKey second)
        {
            return this.RemoveRib(_nodes[first], _nodes[second]);
        }

        public bool RemoveRib(GraphNode<TNodeKey, TNodeValue, TRibValue> first, GraphNode<TNodeKey, TNodeValue, TRibValue> second)
        {
            if (ReferenceEquals(first, null))
            {
                throw new ArgumentNullException("first");
            }
            if (ReferenceEquals(second, null))
            {
                throw new ArgumentNullException("second");
            }
            return first.RemoveRibTo(second);
        }

        public DirectionalRib<TNodeKey, TNodeValue, TRibValue> GetRib(GraphNode<TNodeKey, TNodeValue, TRibValue> first, GraphNode<TNodeKey, TNodeValue, TRibValue> second)
        {
            if (ReferenceEquals(first, null))
            {
                throw new ArgumentNullException("first");
            }
            if (ReferenceEquals(second, null))
            {
                throw new ArgumentNullException("second");
            }
            return first.GetRibTo(second);
        }

        public DirectionalRib<TNodeKey, TNodeValue, TRibValue> GetRib(TNodeKey first, TNodeKey second)
        {
            return this.GetRib(_nodes[first], _nodes[second]);
        }

        public bool TryGetRib(GraphNode<TNodeKey, TNodeValue, TRibValue> first, GraphNode<TNodeKey, TNodeValue, TRibValue> second, out DirectionalRib<TNodeKey, TNodeValue, TRibValue> rib)
        {
            if (ReferenceEquals(first, null))
            {
                throw new ArgumentNullException("first");
            }
            if (ReferenceEquals(second, null))
            {
                throw new ArgumentNullException("second");
            }
            if (first.HasRibTo(second))
            {
                rib = first.GetRibTo(second);
                return true;
            }
            else
            {
                rib = null;
                return false;
            }
        }

        public bool TryGetRib(TNodeKey first, TNodeKey second, out DirectionalRib<TNodeKey, TNodeValue, TRibValue> rib)
        {
            return this.TryGetRib(_nodes[first], _nodes[second], out rib);
        }

        public void Clear()
        {
            _nodes.Clear();
        }

        #endregion
    }
}
